require "behaviours/wander"
require "behaviours/faceentity"
require "behaviours/chaseandattack"
require "behaviours/panic"
require "behaviours/follow"
require "behaviours/attackwall"
require "behaviours/standstill"
require "behaviours/leash"
require "behaviours/runaway"

local ShadowWaxwellBrain = Class(Brain, function(self, inst)
    Brain._ctor(self, inst)
end)
local MIN_FOLLOW_DIST = 0
local MAX_FOLLOW_DIST = 12
local TARGET_FOLLOW_DIST = 6
local START_FACE_DIST = 4
local KEEP_FACE_DIST = 8

local KEEP_DANCING_DIST = 2

local AVOID_EXPLOSIVE_DIST = 5

local function GetLeader(inst)
    return inst.components.follower.leader
end

local function GetLeaderPos(inst)
    return inst.components.follower.leader:GetPosition()
end

local function GetFaceLeaderFn(inst)
    local target = GetLeader(inst)
    return target ~= nil and target.entity:IsVisible() and inst:IsNear(target, START_FACE_DIST) and target or nil
end

local function KeepFaceLeaderFn(inst, target)
    return target.entity:IsVisible() and inst:IsNear(target, KEEP_FACE_DIST)
end

local function GetSpawn(inst)
    return inst.GetSpawnPoint ~= nil and inst:GetSpawnPoint() or nil
end

local function DanceParty(inst)
    inst:PushEvent("dance")
end

local function ShouldDanceParty(inst)
    local leader = GetLeader(inst)
    return leader ~= nil and leader.sg:HasStateTag("dancing")
end

local function ShouldAvoidExplosive(target)
    return target.components.explosive == nil
        or target.components.burnable == nil
        or target.components.burnable:IsBurning()
end

local function ShouldWatchMinigame(inst)
    if inst.components.follower.leader ~= nil and inst.components.follower.leader.components.minigame_participator ~= nil then
        if inst.components.combat.target == nil or inst.components.combat.target.components.minigame_participator ~= nil then
            return true
        end
    end
    return false
end

local function WatchingMinigame(inst)
    return (inst.components.follower.leader ~= nil and inst.components.follower.leader.components.minigame_participator ~= nil) and
        inst.components.follower.leader.components.minigame_participator:GetMinigame() or nil
end

local function WatchingMinigame_MinDist(inst)
    local minigame = WatchingMinigame(inst)
    return minigame ~= nil and minigame.components.minigame.watchdist_min or 0
end
local function WatchingMinigame_TargetDist(inst)
    local minigame = WatchingMinigame(inst)
    return minigame ~= nil and minigame.components.minigame.watchdist_target or 0
end
local function WatchingMinigame_MaxDist(inst)
    local minigame = WatchingMinigame(inst)
    return minigame ~= nil and minigame.components.minigame.watchdist_max or 0
end

local function CreateWanderer(self, maxdist)
    return Wander(self.inst,
        function() return GetSpawn(self.inst) end,
        maxdist,
        nil, nil, nil, nil,
        { -- Small wander radius with a dapper stroll.
            should_run = false,
            wander_dist = 4,
        }
    )
end

local function CreateIdleOblivion(self, delay, range)
    range = range * range
    return LoopNode {
        WaitNode(delay),
        ActionNode(function()
            local leader = GetLeader(self.inst)
            local spawnpt = GetSpawn(self.inst)
            --NOTE: range is squared already
            if leader ~= nil and spawnpt ~= nil and leader:GetDistanceSqToPoint(spawnpt) >= range then
                self.inst:PushEvent("seekoblivion")
            end
        end),
    }
end

function ShadowWaxwellBrain:OnStart()
    -- Common AI for most of the shadow minions.
    local watch_game = WhileNode(function() return ShouldWatchMinigame(self.inst) end, "Watching Game",
        PriorityNode({
            Follow(self.inst, WatchingMinigame, WatchingMinigame_MinDist, WatchingMinigame_TargetDist,
                WatchingMinigame_MaxDist),
            RunAway(self.inst, "minigame_participator", 5, 7),
            FaceEntity(self.inst, WatchingMinigame, WatchingMinigame),
        }, 0.25))

    local dance_party = WhileNode(function() return ShouldDanceParty(self.inst) end, "Dance Party",
        PriorityNode({
            Leash(self.inst, GetLeaderPos, KEEP_DANCING_DIST, KEEP_DANCING_DIST),
            ActionNode(function() DanceParty(self.inst) end),
        }, 0.25))

    local avoid_explosions = RunAway(self.inst,
        { fn = ShouldAvoidExplosive, tags = { "explosive" }, notags = { "INLIMBO" } }, AVOID_EXPLOSIVE_DIST,
        AVOID_EXPLOSIVE_DIST)

    local face_leader = FaceEntity(self.inst, GetFaceLeaderFn, KeepFaceLeaderFn)

    -- Select which shadow Waxwell brain to focus on based off of prefab.
    local root = nil
    root = PriorityNode({ -- This protector is set to defend an area and then vanish.
        -- Fun stuff.
        dance_party,
        watch_game,
        -- Keep watch out for immediate danger.
        avoid_explosions,
        -- Attack.
        ChaseAndAttack(self.inst),

        Follow(self.inst, function() return self.inst.components.follower.leader end, MIN_FOLLOW_DIST, TARGET_FOLLOW_DIST,
            MAX_FOLLOW_DIST),
        -- Wander around and stare.
        face_leader,
    }, 0.25)

    self.bt = BT(self.inst, root)
end

function ShadowWaxwellBrain:OnInitializationComplete()
    if self.inst.SaveSpawnPoint ~= nil then
        self.inst:SaveSpawnPoint(true) --true: dont_overwrite
    end
end

return ShadowWaxwellBrain
